//
//  server.hpp
//  fibio
//
//  Created by Chen Xu on 14-3-13.
//  Copyright (c) 2014 0d0a.com. All rights reserved.
//

#ifndef fibio_http_server_server_hpp
#define fibio_http_server_server_hpp

#include <memory>
#include <string>
#include <functional>
#include <system_error>
#include <fibio/stream/iostream.hpp>
#include <fibio/stream/ssl.hpp>
#include <fibio/http/server/request.hpp>
#include <fibio/http/server/response.hpp>

namespace fibio {
namespace http {

constexpr unsigned DEFAULT_MAX_KEEP_ALIVE = 100;
#if defined(_WIN32)
// NOTE: std::chrono::seconds constructor is not constexpr in VC
static const timeout_type DEFAULT_TIMEOUT = std::chrono::seconds(60);
static const timeout_type NO_TIMEOUT = std::chrono::seconds(0);
#else
constexpr timeout_type DEFAULT_TIMEOUT = std::chrono::seconds(60);
constexpr timeout_type NO_TIMEOUT = std::chrono::seconds(0);
#endif

struct server_error : std::runtime_error
{
    server_error(http_status_code c) : std::runtime_error(""), code(c) {}

    server_error(http_status_code c, const std::string& msg) : std::runtime_error(msg), code(c) {}

    server_error(http_status_code c, const char* msg) : std::runtime_error(msg), code(c) {}

    server_error(http_status_code c, const std::string& msg, const common::header_map& hdrs)
    : std::runtime_error(msg), code(c), additional_headers(hdrs)
    {
    }

    http_status_code code;
    common::header_map additional_headers;
};

struct server
{
    typedef fibio::http::server_request request;
    typedef fibio::http::server_response response;
    typedef std::function<bool(request& req, response& resp)> request_handler;

    struct settings
    {
        std::string address_ = "0.0.0.0";
        unsigned short port_ = 80;
        request_handler default_request_handler_
            = [](request&, response&) -> bool { return false; };
        timeout_type read_timeout_ = DEFAULT_TIMEOUT;
        timeout_type write_timeout_ = DEFAULT_TIMEOUT;
        unsigned max_keep_alive_ = DEFAULT_MAX_KEEP_ALIVE;
        ssl::context* ctx_ = nullptr;
    };

    server() = default;

    server(unsigned short p) { port(p); }

    server(const std::string& a, unsigned short p) { address(a).port(p); }

    ~server();

    server& address(const std::string& a)
    {
        s_.address_ = a;
        return *this;
    }

    server& port(unsigned short p)
    {
        s_.port_ = p;
        return *this;
    }

    server& ssl(ssl::context& c)
    {
        s_.ctx_ = &c;
        return *this;
    }

    server& timeout(timeout_type t)
    {
        s_.read_timeout_ = s_.write_timeout_ = t;
        return *this;
    }

    server& max_keepalive(unsigned m)
    {
        s_.max_keep_alive_ = m;
        return *this;
    }

    server& handler(request_handler h)
    {
        s_.default_request_handler_ = h;
        return *this;
    }

    server& start();

    void stop();

    boost::system::error_code join();

    boost::system::error_code run() { return start().join(); }

    struct impl;

private:
    void init_engine();

    bool ssl() const { return s_.ctx_; }

    settings s_;
    impl* engine_ = nullptr;
    std::unique_ptr<fiber> servant_;
};

} // End of namespace http
} // End of namespace fibio

#endif
